06 - Embedded Programming
Programming a Microcontroller
With this weeks topic, the second lecture of a series of lectures about electronics is started. The previous was electronics production, where we partially designed but mainly produced our own PCB by milling, stuffing and soldering. This week was about programming the mircocontroller on the board we made.
This weeks's assignment were:
- Group Assignment
- Browse through the data sheet for your microcontroller
- Compare the performance and development workflows for other architectures
- Individual Assignment
- Write a program for a microcontroller development board that you made ...
- ... to interact (with local input and/or output devices)
- ... and communicate (with remote wired and/or wireless devices)
- For extra credit use different languages and/or development environments
- For extra credit connect external components to the board
- Write a program for a microcontroller development board that you made ...
Group Assignment
For the group assignment, we each read the datasheet for our and then compared the performance of development workflows to other architectures. You can find our results here.
For our PCBs we used the Seeed Studio XIAO Seeeduino. It does not have a data sheet (only a user manual) but supplies most of the necessary information in a wiki entry. It carries a powerful microcontroller, namely ATSAMD21-G18A-MU. For this, a data sheet is available online via this link. Therefore, we did not only carefully read the wiki entry but also the data sheet for the microcontroller.
When opening the data sheet for the microcontroller, I was surprised that the number of pages exceedes one thousand. Apparently, this is quite common according to my instructor. However, reading all of the pages would not only be very time intensive but also might not yield wanted information. Therefore, it is important to filter and only read the sections about some key features. Below, I have summarized which specifications should be known, the reason(s) for it and what information I acquired from it for the ATSAMD21-G18A-MU. In addition to these, I highly recommend to read the section about the features, the description and the configuration summary in the very beginning as they supply the reader with many essential information on few pages.
Section | Information | Details for ATSAMD21-G18A-MU |
---|---|---|
Schematics/Functional Block Diagram | Architecture, components, instances and functions of pins | (Just a general overview) |
Pinout | Pin implementations and functions - Which pins are available? | E.g. input vs. output, analog vs. digital, power/ground, serial communication, 38 general purpose inputs/outputs (GPIOs) |
Pin Multiplexing | Options for selecting a single function for a single physical pin - What function can the pin have? | Selection in Software e.g. for pin number 8: input/output port B, powered with voltage level "VDDANA", external interrupt, analog input, analog PTC (Peripheral Touch Controller) input, serial communication, waveform output of time counter |
Power Supply | Power supply to microcontroller, supply of pins | Operating voltage from 1.62 V to 3.63 V |
Memories | Size of memory, computing velocity | 256 KB Flash, 32 KB SRAM, 64 KB peripherals, 32-bit |
PM - Power Manager | Power modes with different active or stopped processes | Power mode (active) and two sleep modes (IDLE with 3 levels and STANDBY) |
SERCOM - Serial Communication Interface | Available communication protocols | I2C, USART, SPI |
ADC, AC and DAC | ADC resolution, conversion rate, conversion range | ADC resolution: 8-, 10- or 12-bit (default); DAC resolution: 10-bit; conversion rate: 350 kilo samples per second (kps); DAC conversion between ground and Vref |
Electrical Characteristics | Clock frequency, current consumption | 48 MHz; Consumption in IDLE mode 1.17 mA in Active IN 10.3 mA |
Packaging Information | Package size, pin size + distance, thermal resistance of package, soldering profile | 1.2 x 9 x 9 mm package, 0.25 mm pin width, 0.5 mm distance, 32°C/W, max. soldering temperature of 260°C |
The Seeed Studio XIAO Seeeduino however only carries the ATSAMD21-G18A-MU microcontroller but also adds another board layout on top which changes some properties. Most importantly, the GPIOs are narrowed down to eleven, all of which can be used as analog or digital input or outputs. In addition, some of these pins can be used for serial communication either for I2C (SDA & SLA), for USART (Rx & Tx) and SPI (MISO, MOSI & SCK). Furthermore, there is one pin capable of digital to analog conversion. Lastly, several pins can be used to sense minor capacity changes e.g. in case of touch, called QTouch.
In addition to the GPIOs, there are three other pins, one of them supplies ground, and the other two a power of 5V and 3.3V, respectively. The detailed pinout of the Seeed Studio XIAO Seeeduino is shown below.
Due to the additional layout, the board has slightly increased in size and now has the dimensions of 20 × 17.5 × 3.5 mm. Additionally, the microcontroller can now be connected to a device via USB Type-C.
As a last important aspect, the reset pins should be mentioned. In case they are short circuited twice, shortly behind each other, the chip enters the so called "Bootloader Mode". The Seeeduino has two partitions, one is for the the actual program coded by the user and the other for the bootloader. By entering the bootloader mode, the second partition can be accessed. This is useful in case a program has failed. In addition, in case the reset pins are short circuited only once, the first partition is reset.
Individual Assignment
The individual assignment consisted of programming a board to make it firstly interact with local input and output devices and secondly to communicate with a remote device. I had done both of this before, actually several times. For exampled, I have programmed an Arduino Nano to read the analog data of a strain gauge and a load cell which is sent to a computer via serial communication where the data is saved along with a time tag of the data recoded.
However, just repeating steps and documenting them here would be a boring assignment with nothing to learn. Therefore, I wanted to challenge myself a bit and use a different programming language and environment. I chose Python as the language and Microsoft's Visual Studio Code, which I have both used before. Python, in my eyes, is quite a lot easier to program than C/C++ but can consume too much memory. Luckily, the microcontroller that I have soldered to my PCB is capable of Python.
Interacting with Input and Output Devices
Interacting with local input and output devices is not really challenging for me. There are analog and digital devices. Digital input and outputs only have two states, namely one or zero which represent either a low or a high voltage level, respectively. What a low and high voltage level depends on the voltage used to power the microcontroller. Analog signals in contrast can take any value between the low and high voltage level and by this have many different states. The number of these states is given by the resolution of the analog signal, which is specific to the microcontroller.
For this assignment, I will be using the button as an input device and the LEDs on the PCB and the microcontroller as an output device to interact with. The button is a digital device. The pin which reads the state of the button, namely the "D0" pin, is connected to 5V via a pull-up resistor. Only if it is pressed, the pin is connected to the ground. Therefore, the button is active when a low voltage is read at the pin.
Also, the LEDs are digital devices. In contrast to the button, the LEDs need a high voltage for being activated. One exception however is the built-in LED of the microcontroller I am using on my PCB, the Seeed Studio XIAO SAMD21. It actually works vice versa according to its data sheet.
Embedded Programming with Arduino IDE
One way, probably the most popular method of programming the SamD21 microcontroller is to use the Arduino IDE as it is the go-to method described in the wiki entry of the Seeed Studio XIAO SAMD21. In this section of the wiki entry, it is described how to get started by using the Arduino IDE.
For a previous assignment, i.e. in week four electronics production during the test phase, I have already programmed the SAMD21. I used the Arduino IDE to uploaded basic scripts to the SAMD21 to test its functionality.
To setup the Arduino IDE and use it to program the SAMD21, I simply followed the steps that are described here on the wiki website in the "Getting Started" section. Furthermore, I uploaded two scripts to the microcontroller. The first turns on an LED, waits a second, turns it off and waits another second until it is repeated. The second script turned the LED on every time the button is pressed. Both scripts worked right away without any problems. For a more detailed documentation about the test phase using the Arduino IDE, please refer to this section.
Embedded Programming with CircuitPython
As I had done embedded programming using the Arduino IDE already previously, in fact many times, I wanted to challenge myself to learn something new. Here, a friend who did FabAcademy two years ago proposed to use Python. I immediately was really excited about it as I love Python. It makes coding a lot easier, at least in my eyes. Therefore, I started right away and looked for instructions on how to get started with it. I found a link on the wiki entry about how to get started with CircuitPython leading to this website.
For using Python as a programming language to program the microcontroller, I firstly had to download the bootloader (.uf2) of CircuitPro for the Seeed Studio XIAO SAMD21 from here. Then, I plugged the SAMD21 into my computer and made it enter the bootloader mode by short connecting the reset (RST) pins twice using a male to male jumper cable.
Immediately after this, my computer made the sound of a disconnecting external drive. In fact, an external drive appeared called "Arduino (D:)".
Next, I copied the previously downloaded bootloader file and pasted it onto the external drive "Arduino (D:)". Just after pasting the file, my computer made the disconnecting sound again followed by a connecting sound. The previously called "Arduino (D:)" was now called "CIRCUITPY (D:)". It now had a lot less memory available.
In addition to a new name, the external device called "CIRCUITPY (D:)" also had new files on it. Here, I immediately recognized the code.py file as a script coded in the programming language Python. Any code that I want to upload to the microcontroller can now be simply saved on "CIRCUITPY (D:)" with the name code.py.
After having completed these steps, it is recommended on the
website
with the instructions to upload
the code shown below to the microcontroller to check wether CircuitPython is functional. For this, I opened the
code.py file with VS Code and pasted the code into the editor. Then, I only added some comments
with "#" to explain what the code is doing in a specific line and saved the file by pressing Ctrl + S
import time
import board
from digitalio import DigitalInOut, Direction
# Declare and initialize led pin
led = DigitalInOut(board.LED_INVERTED)
# Set led pin to output
led.direction = Direction.OUTPUT
while True:
led.value = True # Turn led off LED (INVERTED!)
time.sleep(1) # Wait 1s
led.value = False # Turn led on
time.sleep(1) # Wait 1s
This code basically does the very same thing as the first code of testing using the Arduino IDE. It starts with initializing the pin of the LED, setting it up as an output and finally giving it the value "True" and "False" alternatingly with a pause of one second between the switches. By this, the LED is turned on and off for a second, each.
After saving this code and by this uploading it to the microcontroller, I noticed that this affected the built-in LED
on the microcontroller. Hence, I changed led = DigitalInOut(board.LED_INVERTED)
led = DigitalInOut(board.D8)
As a second script, I wanted to imitate the behavior of the second Arduino script that I used for testing, namely turning the led on every time the button is pressed and off when the button is not pressed. I am very familiar with all kinds of programming languages, including Python, and therefore it was very easy for me to achieve it. This is the code I programmed:
import board
from digitalio import DigitalInOut, Direction
# Declare and initialize led and button pins
led = DigitalInOut(board.D8)
button = DigitalInOut(board.D0)
# Set led pin to output and button pin to input
led.direction = Direction.OUTPUT
button.direction = Direction.INPUT
while True:
if button.value == True:
# if button is not pressed (INVERTED)
led.value = False
else:
# if pressed
led.value = True
The only thing I had to keep in mind while programming is that the "True" state of the button refers to it not being pressed not pressed. "True" here is equivalent to "HIGH" in the Arduino IDE meaning a high voltage level for digital pins. In contrast, "False" or "LOW" are present for low voltage levels.
After saving this code on the SAMD21, the LED was on every time the button was pressed and off in case it was not, just as expected.
However, I also wanted to program a script that is more challenging. A common aspect of using embedded programming is using a counter, which I wanted to imply as well. Therefore, my goal for this script was to make the LED blink as many times as the button has been pressed previously.
For this, I needed an integer acting as a counter to keep track of how many times, a button is pressed. In case, the button is pressed, the counter needs to be advanced by one. To code it, I started with the previous script where I deleted the else statement and set the if contition to "False" instead of "True". Additionally, I declared and initialized a counter that is advanced inside of the if statement.Furthermore, after advancing the counter, I programmed a for-loop that is executed as many times as the value of the counter. Inside of the for-loop is a part of the example where the LED blinks continuously, just with a delay decreased to 0.15 s. For this I also had to add "import time" in the top again.
import time
import board
from digitalio import DigitalInOut, Direction
# Declare and initialize led and button pins
led = DigitalInOut(board.D8)
button = DigitalInOut(board.D0)
# Set led pin to output and button pin to input
led.direction = Direction.OUTPUT
button.direction = Direction.INPUT
# Declare and initialize counter
counter = 0
while True:
if button.value == False:
# if button is pressed (INVERTED)
counter += 1 # Advance counter
for i in range(counter):
# For as many times as the value of counter, do
led.value = True # Turn led off LED (INVERTED!)
time.sleep(0.15) # Wait 1s
led.value = False # Turn led on
time.sleep(0.15) # Wait 1s
However, the for loop is entered as soon as the button is pressed. However, I wanted it to be entered when
the button is released. Therefore, I added a while loop right before the for-loop that is true as long
as the button is pressed. Inside of it, nothing is executed, i.e. there is a sleep
while button.value == False:
time.sleep(0.01)
Next, I wanted to be able to reset the counter. For this, I decided the reset signal was pressing the
button for more than a second. Therefore, I needed to be able to tell, how long the button was
pressed. From previous experiences, I know that the Arduino IDE has a function called
millis()
time.monotonic()
import time
import board
from digitalio import DigitalInOut, Direction
# Declare and initialize led and button pins
led = DigitalInOut(board.D8)
button = DigitalInOut(board.D0)
# Set led pin to output and button pin to input
led.direction = Direction.OUTPUT
button.direction = Direction.INPUT
# Declare and initialize counter
counter = 0
while True:
if button.value == False:
start = time.monotonic() # Time at start of pressing button
# if button is pressed (INVERTED)
counter += 1 # Advance counter
while button.value == False:
time.sleep(0.01)
stop = time.monotonic() # Time at stopping pressing button
if stop-start <= 1:
# If button was pressed less than a second
for i in range(counter):
# For as many times as the value of counter, do
led.value = True # Turn led off LED (INVERTED!)
time.sleep(0.15) # Wait 1s
led.value = False # Turn led on
time.sleep(0.15) # Wait 1s
else:
# Reset counter
counter = 0
With the code above, I was able to achieve what I wanted, namely the LED blinks as many times as the button was pressed. Furthermore, the counter keeping track of how many times the button was pressed can be reset. Have a look yourself how this looks and works:
LED Blinks as Many Times as Button Was Pressed
Resetting the Counter
Communication with Remote Device
Establishing a serial communication between the host computer and the microcontroller can easily be done using the Arduino IDE as it is very beginner-friendly. I would recommend it for people who are new to embedded programming. However, I have used it several times and it gets quite boring. Nevertheless, I wanted to show to you (and to my forgetful future me), how easily it is done. In addition to that however, I wanted learn something new and use CircuitPython and my all-time favorite programming IDE, Microsoft's Visual Studio Code.
Serial Communication with Arduino IDE
Establishing a serial communication between a microcontroller and the host computer using the Arduino IDE is very easy. It only needs a functional code that includes reactions to incoming serial messages as well as some output. However, I firstly had to switch back to Arduino from CircuitPython. Nevertheless, this was easily done by entering the bootloader mode (see above). Then, the microcontroller was capable of working with the Arduino IDE as the booloader is uploaded with the script.
Then, I started developing the code. As an LED is the only output device available on the board, I manipulated it though the microcontroller. More precisely, I wanted to switch it on in case "1" was sent via the serial communication and switch it of for a "0".
The first step to develop this code was to establish a serial communication by opening a serial stream in
Arduino. This is easily done with Serial.begin(
Serial.print("")
Serial.println("")
Serial.readString()
Serial.available()
With these code snippets, I furthermore implemented some logic to turn on and off the led in case the if-conditions were fulfilled. This is the complete code:
// declare and initialize pin for LED
const int ledPin = D8;// the number of the LED pin
String value;
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin for LED as an output.
pinMode(ledPin, OUTPUT);
Serial.begin(9600); // Start serial communication
while (!Serial); // Wait until Serial is open
Serial.println("listening..."); // Send that Serial is open and listening
}
// the loop function runs over and over again forever
void loop() {
if (Serial.available() > 0) {
// read the incoming string
value = Serial.readString();
if (value == "1"){
digitalWrite(ledPin, HIGH); // turn the LED on
Serial.println("Message '1' received. Turning LED on.");
}
else if (value == "0"){
digitalWrite(ledPin, LOW); // turn the LED off
Serial.println("Message '0' received. Turning LED off.");
}
else {
Serial.print("Unknown message '");
Serial.print(value);
Serial.println("'. Use '1' and '0' to turn LED on and off.");
}
}
}
Before uploading it to the microcontroller, I selected the right board. The remaining configurations
for the board were still the same as set in this section. Then, I opened the
serial monitor by clicking on Tools > Serial Monitor or by pressing Ctrl + Shift + M
Then, I uploaded the script to the board. The IDE firstly asked be to save but after specifying the location and name, the sketch compiled and uploaded to the microcontroller.
With the script being uploaded, I immediately received the message "listening..." on the serial monitor of the Arduino IDE. Then, I started sending messages to the microcontroller by typing in a message in the bar at the top and pressing enter. In case I sent a "1", the LED switched on and for a "0" the LED switched off. Have a look yourself in the video. On the screen you can see the serial monitor and in the front the PCB. Interestingly, every time a serial is received or sent the blue built-in LED is turned on.
LED Switches On and Off According to Serial Input
Serial Communication with VS Code and CircuitPython
After using the Arduino IDE again, I had to configure the microcontroller for CircuitPython once more as I described here. After that, I also configured to setup the programming IDE, VS Code, for a serial communication to a CircuitPython device as unfortunately it is not possible in the default. However, there is an easy tutorial on this website that I followed.
The first step is to download VS Code, which I had done already. Next, I had to install the CircuitPython extension for VS Code. This was of course the newest version v.0.2.0. The third step was to plug-in the PCB into my computer. Then, in VS code, I opened the "CIRCUITPY (D:)" drive as a folder. Here, it is really important to open it as a folder as only then the extension recognizes the code.py file in the workspace to load the environment. With this step, a new folder was created on the drive named ".vscode" where the settings for the workspace are stored in a "settings.json".
From the fifth step on, I was not really able to follow the tutorial anymore. Here, I had to allow dependencies to load but nothing happened. Therefore, I skipped this part. Next was to choose the correct board in the bottom right of the IDE but nothing showed up.
However, it was only for the seventh step, that I
really had an issue with this tutorial. I had to go to the command palette with Ctrl + Shift + P
Therefore, I looked online for a solution. After a while, I found this post on a GitHub forum. Many solutions were proposed which I tried to follow but nothing worked. After several hours, I was close to giving up but then I was at the very last comment where a person proposed to use a previous version of the extension.
For this, I went to the extensions I had installed for VS code and right-clicked on the CircuitPython extension. Here, I selected "Install Another Version..." and chose the third to last version v0.1.19.
Immediately, after selecting this version, two messages of VS Code popped up in the bottom left about a bundle downloading and updating. I figured these were the dependencies I had to allow to load in the fifth step of the tutorial. Additionally, I was able to see "Seeed:Seeeduino XIAO" in the right of the status bar, which was potentially the board I had to chose in the sixth step. Lastly, a new terminal called "Circuit Python Serial Monitor" was opened.
I quickly proceeded with the seventh step of the tutorial to see wether the commands can be found now. Again,
pressing Ctrl + Shift + P
Now, that I was able to use VS Code for serial communication with my board, I looked online for a Python script
to let the microcontroller echo he input. It was pretty clear to me that simply using the print()
import supervisor
print("listening...")
while True:
if supervisor.runtime.serial_bytes_available:
value = input().strip()
# Sometimes Windows sends an extra (or missing) newline - ignore them
if value == "":
continue
print("RX: {}".format(value))
I simply copied and pasted this code into code.py on the CIRCUITPY (D:) drive and pressed save. However,
nothing happened and I did not see any "listening..." message in the terminal called "Circuit Python Serial
Monitor". I suspected that the monitor might have missed the very first line of the message and therefore
pasted the line print("listening...")
However, still nothing happened in the terminal. It was not until I discovered, that I had to open the
serial monitor by pressing Ctrl + Shift + P
print()
Then, I started to code my own respond to a message. As there is only one type of output devices, available on
the board, the LEDs, I wanted to switch them on and off when the message "1" and "0" is received. To code it,
I used the imports except for "time" and the setup for the LED pin from the previous script for blinking
the LED connected to the D8 pin. Furthermore, in case the input message which is saved in the variable
value
led.value = True
led.value = False
import supervisor
import board
from digitalio import DigitalInOut, Direction
# Declare and initialize led pin
led = DigitalInOut(board.D8)
# Set led pin to output
led.direction = Direction.OUTPUT
print("listening...")
while True:
if supervisor.runtime.serial_bytes_available:
value = input().strip()
# Sometimes Windows sends an extra (or missing) newline - ignore them
if value == "":
continue
elif value == "1":
led.value = True
print("Message '{}' received. Turning LED on.".format(value))
elif value == "0":
led.value = False
print("Message '{}' received. Turning LED off.".format(value))
else:
print("Unknown message '{}'. Use '1' and '0' to turn LED on and off".format(value))
I only had one issue coding this script, namely in the if statements. Instead of programming it to be
if value == 1
After saving it, I was able to switch on the LED by sending the message "1" via the serial monitor and switch it off with "0". In comparison to the communication via the Arduino IDE, the blue built-in LED does not switch on and the the communication is slightly faster as it reacts immediately.
LED Switches On and Off According to Serial Input
Source Code for Download
- Script I/O Interaction(.py): Python script to make an LED blink as many times as a button was pressed, including a reset function
- Serial Communication Arduino (.ino): Arduino sketch for making an LED switch on and off depending on the serial input
- Serial Communication Python (.py): Python script for making an LED switch on and off depending on the serial input